"""
智能睡眠质量分析系统 - 软件著作权申请版本
版权所有 (c) 2023-2025 脑科技公司
版本号: 2.0.0
开发日期: 2023-08-15
"""

import sys
import os
import asyncio
import time
import pandas as pd
import numpy as np
import logging
from datetime import datetime, timedelta
from bleak import BleakScanner, BleakClient
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QPushButton, QTextEdit, QFileDialog, QTabWidget,
    QInputDialog, QLineEdit, QStatusBar, QGroupBox, QProgressBar,
    QSplitter, QFrame, QMessageBox, QDialog, QDialogButtonBox,
    QComboBox, QSpinBox, QFormLayout, QStackedWidget
)
from PySide6.QtCore import Qt, QThread, Signal, QObject, QTimer
from PySide6.QtGui import QFont, QPixmap, QColor, QPalette, QIcon
import qasync
from volcenginesdkarkruntime import Ark
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import seaborn as sns
import platform
from psychopy import visual, core, monitors
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import IsolationForest
from scipy.signal import welch
from sklearn.svm import SVC
from sklearn.pipeline import make_pipeline

from min_naoji import MainWindow

# ================== 系统配置 ==================
# 创建日志目录
os.makedirs("logs", exist_ok=True)
log_file = os.path.join("logs", f"sleep_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(log_file),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger()

# 版权信息
COPYRIGHT_INFO = """
智能睡眠质量分析系统
版本: 2.0.0
版权所有 © 2023-2025 脑科技公司
本软件受著作权法和国际条约保护
"""

# 系统信息
SYSTEM_INFO = f"""
系统平台: {platform.platform()}
Python版本: {platform.python_version()}
处理器: {platform.processor()}
"""

# 脑电波类型映射
EEG_BANDS = {
    "Delta": (0.5, 4),
    "Theta": (4, 8),
    "Alpha": (8, 12),
    "Beta": (12, 30),
    "Gamma": (30, 45)
}

# 睡眠阶段映射
SLEEP_STAGES = {
    0: "清醒 (Wake)",
    1: "浅睡 (N1)",
    2: "中睡 (N2)",
    3: "深睡 (N3)",
    4: "REM睡眠"
}

# 睡眠质量评分标准
SCORE_CRITERIA = {
    "sleep_efficiency": {"weight": 0.3, "desc": "睡眠效率（总睡眠时间/卧床时间）"},
    "n3_percentage": {"weight": 0.25, "desc": "深睡占比（N3阶段时长占比）"},
    "rem_percentage": {"weight": 0.2, "desc": "REM睡眠占比"},
    "awakenings": {"weight": 0.15, "desc": "夜间觉醒次数（每次觉醒扣分）"},
    "latency": {"weight": 0.1, "desc": "入睡潜伏期（超过30分钟扣分）"}
}


# ================== 睡眠分期模型 ==================
class SleepStageAnalyzer:
    """睡眠分期分析器"""

    def __init__(self):
        self.model = make_pipeline(
            StandardScaler(),
            SVC(kernel='rbf', C=1.0, gamma='scale', probability=True)
        )
        self.is_trained = False
        logger.info("睡眠分析器初始化完成")

    def preprocess_data(self, eeg_data):
        """预处理EEG数据"""
        # 1. 数据清洗
        clf = IsolationForest(contamination=0.05)
        outliers = clf.fit_predict(eeg_data)
        clean_data = eeg_data[outliers == 1]

        # 2. 标准化
        scaled_data = StandardScaler().fit_transform(clean_data)

        # 3. 特征提取（频域特征）
        features = []
        for i in range(scaled_data.shape[0]):
            f, pxx = welch(scaled_data[i, :], fs=128, nperseg=256)
            band_features = []
            for band, (low, high) in EEG_BANDS.items():
                mask = (f >= low) & (f <= high)
                band_features.append(np.log10(pxx[mask].mean() + 1e-10))
            features.append(band_features)

        return np.array(features)

    def predict_stages(self, eeg_data):
        """预测睡眠阶段"""
        try:
            # 预处理数据
            features = self.preprocess_data(eeg_data)

            # 简单规则预测（实际应用中应替换为训练好的模型）
            # 这里使用简化版的规则预测
            preds = []
            for feat in features:
                delta, theta, alpha, beta, gamma = feat

                # 简单规则判断
                if delta > theta and delta > alpha:  # Delta波主导
                    preds.append(3)  # 深睡
                elif alpha > beta and alpha > theta:  # Alpha波主导
                    preds.append(0)  # 清醒
                elif theta > alpha and theta > beta:  # Theta波主导
                    if gamma > beta:  # REM睡眠特征
                        preds.append(4)  # REM
                    else:
                        preds.append(1)  # 浅睡
                else:
                    preds.append(2)  # 中睡

            return np.array(preds)

        except Exception as e:
            logger.error(f"睡眠分期预测失败: {str(e)}")
            return np.zeros(len(eeg_data))


# ================== 睡眠质量评分引擎 ==================
class SleepQualityScorer:
    """睡眠质量评分引擎"""

    def __init__(self, user_profile):
        self.user = user_profile

    def calculate_score(self, sleep_data):
        """计算睡眠质量评分（百分制）"""
        # 计算关键指标
        metrics = self.calculate_metrics(sleep_data)

        # 基础评分
        base_score = 0
        for key, crit in SCORE_CRITERIA.items():
            value = metrics.get(key, 0)
            if key == "sleep_efficiency":
                # 睡眠效率：90%以上为满分，70%以下为0分
                eff_score = min(max((value - 0.7) / 0.2, 0), 1)  # 70%-90%线性映射
                base_score += crit["weight"] * eff_score * 100
            elif key == "n3_percentage":
                # 深睡占比：年龄调整基准值
                age_factor = max(0, 1 - (self.user.age - 30) * 0.01)  # 30岁后每岁减1%
                n3_base = 0.2 * age_factor
                n3_score = min(value / n3_base, 1.0) if n3_base > 0 else 0
                base_score += crit["weight"] * n3_score * 100
            elif key == "rem_percentage":
                # REM占比：性别调整基准值
                rem_base = 0.25 if self.user.gender == "男" else 0.28
                if self.user.age > 50: rem_base *= 0.9  # 老年人REM减少
                rem_score = min(value / rem_base, 1.0)
                base_score += crit["weight"] * rem_score * 100
            elif key == "awakenings":
                # 觉醒次数：0次满分，3次以上0分
                awaken_score = max(0, 1 - min(value, 3) / 3)
                base_score += crit["weight"] * awaken_score * 100
            elif key == "latency":
                # 入睡潜伏期：10分钟满分，60分钟0分
                latency_score = max(0, 1 - min(value, 60) / 50)  # 10-60分钟线性映射
                base_score += crit["weight"] * latency_score * 100

        # 最终评分
        final_score = min(max(base_score, 0), 100)

        # 评分等级
        if final_score >= 90:
            rating = "优"
        elif final_score >= 75:
            rating = "良"
        elif final_score >= 60:
            rating = "中"
        else:
            rating = "差"

        return {
            "score": round(final_score, 1),
            "rating": rating,
            "metrics": metrics
        }

    def calculate_metrics(self, sleep_data):
        """计算睡眠质量指标（修复语法+逻辑优化）"""
        # 1. 总记录时间（分钟，每30秒1个数据点）
        total_time = len(sleep_data) / 2 if sleep_data else 0  # 空数据时为0

        # 2. 入睡潜伏期（分钟：首个非觉醒阶段的时间）
        # 找到第一个非0阶段的索引，无则取0
        sleep_onset_idx = next(
            (i for i, stage in enumerate(sleep_data) if stage > 0),
            0
        )
        latency = sleep_onset_idx * 0.5  # 每个数据点0.5分钟

        # 3. 觉醒次数（当前是觉醒，前一阶段非觉醒）
        awakenings = 0
        for i in range(1, len(sleep_data)):
            if sleep_data[i] == 0 and sleep_data[i - 1] > 0:
                awakenings += 1

        # 4. 各阶段数据点统计（0-4阶段）
        stage_counts = {
            stage: sum(1 for s in sleep_data if s == stage)
            for stage in range(5)
        }

        # 5. 总睡眠时间（排除觉醒阶段的时间）
        wake_duration = stage_counts.get(0, 0) * 0.5  # 觉醒总时长（分钟）
        sleep_time = max(total_time - wake_duration, 0)  # 避免负数（如数据全为觉醒）

        # 6. 各阶段 **时间占比**（原逻辑错误：应基于时间，而非数据点数量）
        n3_duration = stage_counts.get(3, 0) * 0.5  # N3阶段总时长（分钟）
        rem_duration = stage_counts.get(4, 0) * 0.5  # REM阶段总时长（分钟）

        n3_percentage = n3_duration / total_time if total_time > 0 else 0
        rem_percentage = rem_duration / total_time if total_time > 0 else 0

        # 7. 睡眠效率（总睡眠时长 / 总记录时间）
        efficiency = sleep_time / total_time if total_time > 0 else 0

        return {
            "total_time": total_time,
            "sleep_time": sleep_time,
            "efficiency": efficiency,
            "latency": latency,
            "awakenings": awakenings,
            "n3_percentage": n3_percentage,
            "rem_percentage": rem_percentage,
            "stage_counts": stage_counts
        }

# ================== 用户档案管理 ==================
class UserProfile:
    """用户睡眠档案"""

    def __init__(self, name="", age=30, gender="男", weight=70, height=175):
        self.name = name
        self.age = age
        self.gender = gender
        self.weight = weight  # 体重 (kg)
        self.height = height  # 身高 (cm)
        self.bmi = self.calculate_bmi()
        self.sleep_history = []

    def calculate_bmi(self):
        """计算BMI指数"""
        height_m = self.height / 100
        return self.weight / (height_m ** 2) if height_m > 0 else 0

    def add_sleep_record(self, record):
        """添加睡眠记录"""
        self.sleep_history.append(record)

    def get_sleep_trends(self):
        """分析睡眠趋势"""
        if len(self.sleep_history) < 2:
            return {}

        # 计算平均分变化
        avg_scores = [r['score'] for r in self.sleep_history]
        trend = "稳定"
        if len(avg_scores) > 1:
            last_diff = avg_scores[-1] - avg_scores[-2]
            if abs(last_diff) > 10:
                trend = "显著上升" if last_diff > 0 else "显著下降"
            elif abs(last_diff) > 5:
                trend = "上升" if last_diff > 0 else "下降"

        return {
            "avg_score": np.mean(avg_scores),
            "weekly_avg": np.mean(avg_scores[-7:]) if len(avg_scores) >= 7 else None,
            "trend": trend
        }


# ================== 蓝牙协议处理器 ==================
class EEGDataCollector(QObject):
    """蓝牙脑电数据采集器"""
    data_received = Signal(dict)
    status_updated = Signal(str)
    file_created = Signal(str)
    connection_status = Signal(bool)

    def __init__(self):
        super().__init__()
        self.client = None
        self.buffer = bytearray()
        self.raw_file = None
        self.csv_file = None
        self.is_recording = False
        self.connected = False
        self.device_name = ""
        self.device_address = ""
        self.last_update_time = datetime.now()
        self.data_points = 0

    async def connect_device(self):
        """连接蓝牙设备"""
        try:
            logger.info("正在扫描蓝牙设备...")
            self.status_updated.emit("正在扫描蓝牙设备...")
            device = await BleakScanner.find_device_by_filter(
                lambda d, _: d.name and "JDY-18" in d.name
            )

            if not device:
                logger.warning("未找到JDY-18设备")
                self.status_updated.emit("未找到兼容的脑电设备")
                return False

            self.device_name = device.name
            self.device_address = device.address
            logger.info(f"找到设备: {device.name} ({device.address})")
            self.status_updated.emit(f"找到设备: {device.name}")

            self.client = BleakClient(device)
            await self.client.connect(timeout=15)
            await self.client.start_notify("0000FFE1-0000-1000-8000-00805F9B34FB", self._data_handler)

            self.connected = True
            self.connection_status.emit(True)
            logger.info("设备连接成功")
            self.status_updated.emit("设备已连接 - 准备接收数据")
            return True

        except Exception as e:
            logger.error(f"连接失败: {str(e)}")
            self.status_updated.emit(f"连接失败: {str(e)}")
            self.connection_status.emit(False)
            return False

    def _data_handler(self, sender, data):
        """数据处理回调"""
        self.buffer.extend(data)
        self._process_buffer()

    def _process_buffer(self):
        """处理原始数据"""
        try:
            while len(self.buffer) >= 4:
                # 查找同步头位置
                sync_pos = self.buffer.find(b'\xAA\xAA')

                if sync_pos == -1:
                    # 没有找到同步头，清空缓冲区
                    self.buffer.clear()
                    return

                if sync_pos > 0:
                    # 删除同步头前的无效数据
                    del self.buffer[:sync_pos]
                    continue

                if len(self.buffer) < 32:
                    # 数据不足一个完整包
                    return

                # 提取有效数据包
                packet = bytes(self.buffer[:32])
                del self.buffer[:32]

                # 写入原始数据文件
                if self.raw_file is not None:
                    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
                    self.raw_file.write(f"{timestamp}|{packet.hex()}\n")

                # 解析数据包
                parsed = self._parse_packet(packet)

                # 发射实时数据信号
                if parsed:
                    self.data_points += 1
                    self.data_received.emit(parsed)

                    # 写入CSV文件（如果正在记录）
                    if self.csv_file is not None:
                        self.csv_file.write(
                            f"{parsed['时间']},{parsed['Delta']},{parsed['Theta']},{parsed['Alpha']},{parsed['Beta']},{parsed['Gamma']},{parsed['信号质量']}\n")

                # 每10个数据点更新一次状态
                if self.data_points % 10 == 0:
                    self.status_updated.emit(
                        f"已接收 {self.data_points} 个数据点 | 质量: {parsed['信号质量'] if parsed else 'N/A'}")

        except Exception as e:
            logger.error(f"数据处理错误: {str(e)}")
            self.status_updated.emit(f"数据处理错误: {str(e)}")

    def _parse_packet(self, packet):
        """解析数据包"""
        try:
            # 添加数据长度校验
            if len(packet) < 20:
                return None

            # 提取并验证数据
            delta = int.from_bytes(packet[4:7], byteorder='big') % 1000000
            theta = int.from_bytes(packet[7:10], byteorder='big') % 1000000
            alpha = int.from_bytes(packet[10:13], byteorder='big') % 1000000
            beta = int.from_bytes(packet[13:16], byteorder='big') % 1000000
            gamma = int.from_bytes(packet[16:19], byteorder='big') % 1000000
            quality = packet[1] if len(packet) > 1 else 0

            # 数据有效性检查
            if any(val < 0 for val in [delta, theta, alpha, beta, gamma, quality]):
                return None

            return {
                "时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3],
                "Delta": delta,
                "Theta": theta,
                "Alpha": alpha,
                "Beta": beta,
                "Gamma": gamma,
                "信号质量": quality
            }
        except Exception as e:
            logger.error(f"数据包解析失败: {str(e)}")
            return None

    def start_recording(self, base_path):
        """开始记录"""
        try:
            # 确保目录存在
            os.makedirs(os.path.dirname(base_path), exist_ok=True)

            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            self.raw_file = open(f"{base_path}_raw.txt", "w", encoding="utf-8")
            self.csv_file = open(f"{base_path}_data.csv", "w", encoding="utf-8")
            self.csv_file.write("时间,Delta,Theta,Alpha,Beta,Gamma,信号质量\n")
            self.is_recording = True
            self.file_created.emit(f"{base_path}_data.csv")
            self.data_points = 0
            logger.info(f"开始记录到: {base_path}")
            return True
        except Exception as e:
            logger.error(f"开始记录失败: {str(e)}")
            self.status_updated.emit(f"记录失败: {str(e)}")
            return False

    def stop_recording(self):
        """停止记录"""
        try:
            # 处理剩余缓冲区数据
            if self.buffer:
                self._process_buffer()

            if self.raw_file:
                self.raw_file.close()
                self.raw_file = None
            if self.csv_file:
                self.csv_file.close()
                self.csv_file = None
            self.is_recording = False
            logger.info("记录已停止")
            return True
        except Exception as e:
            logger.error(f"停止记录失败: {str(e)}")
            return False


# ================== 睡眠分析处理器 ==================
class SleepAnalyzer(QThread):
    """睡眠质量分析线程"""
    analysis_complete = Signal(dict)  # 返回完整分析结果
    progress_updated = Signal(int)
    status_message = Signal(str)

    def __init__(self, csv_path, user_profile, api_key):
        super().__init__()
        self.csv_path = csv_path
        self.user = user_profile
        self.api_key = api_key
        self.report_path = os.path.splitext(self.csv_path)[0] + "_report.md"
        self.stage_analyzer = SleepStageAnalyzer()
        self.scorer = SleepQualityScorer(user_profile)
        logger.info(f"分析器初始化，用户: {user_profile.name}, CSV路径: {csv_path}")

    def run(self):
        """执行分析流程"""
        try:
            self.status_message.emit("开始分析睡眠数据...")
            self.progress_updated.emit(10)

            # 数据加载
            try:
                df = pd.read_csv(self.csv_path, encoding='utf-8')
            except UnicodeDecodeError:
                try:
                    df = pd.read_csv(self.csv_path, encoding='gbk')
                except:
                    df = pd.read_csv(self.csv_path, encoding='ISO-8859-1')

            # 数据预处理
            self.status_message.emit("预处理EEG数据...")
            self.progress_updated.emit(20)
            eeg_data = df[['Delta', 'Theta', 'Alpha', 'Beta', 'Gamma']].values

            # 睡眠分期
            self.status_message.emit("进行睡眠分期分析...")
            self.progress_updated.emit(40)
            sleep_stages = self.stage_analyzer.predict_stages(eeg_data)

            # 睡眠质量评分
            self.status_message.emit("计算睡眠质量评分...")
            self.progress_updated.emit(60)
            score_result = self.scorer.calculate_score(sleep_stages)

            # 生成报告
            self.status_message.emit("生成分析报告...")
            self.progress_updated.emit(80)
            report_content = self._generate_report(df, sleep_stages, score_result)

            # 保存报告
            os.makedirs(os.path.dirname(self.report_path), exist_ok=True)
            with open(self.report_path, "w", encoding="utf-8") as f:
                f.write(report_content)

            self.progress_updated.emit(100)
            self.status_message.emit("分析完成")

            # 构建结果
            result = {
                "report_content": report_content,
                "report_path": self.report_path,
                "sleep_stages": sleep_stages,
                "score": score_result,
                "user": self.user.__dict__
            }

            self.analysis_complete.emit(result)

        except Exception as e:
            error_msg = f"分析失败: {str(e)}"
            logger.exception(error_msg)
            self.status_message.emit(error_msg)
            self.analysis_complete.emit({"error": error_msg})

    def _generate_report(self, df, sleep_stages, score_result):
        """生成睡眠分析报告"""
        # 睡眠阶段分布可视化
        plot_path = os.path.splitext(self.csv_path)[0] + "_stages.png"
        self._generate_sleep_plot(sleep_stages, plot_path)

        # 睡眠阶段统计
        stage_counts = {SLEEP_STAGES[stage]: count for stage, count in enumerate(np.bincount(sleep_stages))}
        stage_stats = "\n".join([f"- {stage}: {count}次 ({count / len(sleep_stages) * 100:.1f}%)"
                                 for stage, count in stage_counts.items()])

        # 睡眠评分详情
        score_details = "\n".join([
            f"- **{crit['desc']}**: {score_result['metrics'][key] * 100 if 'percentage' in key else score_result['metrics'][key]:.1f}"
            f" → 得分: {score_result['metrics'].get(key, 0) * crit['weight'] * 100:.1f}"
            for key, crit in SCORE_CRITERIA.items()
        ])

        # AI分析
        ai_content = ""
        try:
            if not self.api_key:
                raise ValueError("API密钥为空")

            client = Ark(api_key=self.api_key)
            prompt = (
                f"用户信息: {self.user.age}岁{self.user.gender}, BMI: {self.user.bmi:.1f}\n"
                f"睡眠评分: {score_result['score']}/100 ({score_result['rating']})\n"
                f"睡眠阶段分布:\n{stage_stats}\n\n"
                "请用Markdown格式输出一份详细的睡眠分析报告，包括：\n"
                "1. 睡眠质量总体评价\n"
                "2. 主要优势与存在问题分析\n"
                "3. 针对用户特征的个性化改善建议\n"
                "4. 生活习惯调整方案\n"
                "注意：使用专业术语但保持语言简洁易懂。"
            )

            response = client.chat.completions.create(
                model="doubao-1.5-thinking-pro-250415",
                messages=[{"role": "user", "content": prompt}]
            )

            ai_content = response.choices[0].message.content
        except Exception as e:
            ai_content = f"## AI分析引擎调用失败\n\n错误信息: {str(e)}\n\n请检查网络连接和API密钥"

        # 报告内容
        report_content = (
            f"# 睡眠质量分析报告\n\n"
            f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
            f"## 用户信息\n"
            f"- 姓名: {self.user.name}\n"
            f"- 年龄: {self.user.age}岁\n"
            f"- 性别: {self.user.gender}\n"
            f"- BMI: {self.user.bmi:.1f} ({self._get_bmi_category(self.user.bmi)})\n\n"
            f"## 睡眠质量评分\n"
            f"**综合评分**: {score_result['score']}/100\n"
            f"**评级**: {score_result['rating']}\n\n"
            f"### 评分详情\n{score_details}\n\n"
            f"## 睡眠阶段分布\n"
            f"![睡眠阶段图]({os.path.basename(plot_path)})\n\n"
            f"### 阶段统计\n{stage_stats}\n\n"
            f"## 专业分析与建议\n{ai_content}"
        )

        return report_content

    def _generate_sleep_plot(self, sleep_stages, save_path):
        """生成睡眠阶段可视化图"""
        plt.figure(figsize=(12, 6))
        plt.style.use('seaborn-darkgrid')

        # 时间轴（分钟）
        time_points = np.arange(len(sleep_stages)) * 0.5

        # 创建颜色映射
        stage_colors = {
            0: '#3498db',  # 清醒 - 蓝色
            1: '#2ecc71',  # N1 - 绿色
            2: '#f1c40f',  # N2 - 黄色
            3: '#e74c3c',  # N3 - 红色
            4: '#9b59b6'  # REM - 紫色
        }

        # 绘制睡眠阶段
        for i in range(len(sleep_stages) - 1):
            stage = sleep_stages[i]
            plt.fill_between(
                [time_points[i], time_points[i + 1]],
                0, 1,
                color=stage_colors[stage],
                alpha=0.7
            )

        # 添加图例
        legend_handles = []
        for stage, color in stage_colors.items():
            legend_handles.append(plt.Rectangle((0, 0), 1, 1, fc=color, alpha=0.7))

        plt.legend(legend_handles, SLEEP_STAGES.values(),
                   loc='upper right', ncol=5, frameon=True)

        plt.title('睡眠阶段分布', fontsize=14)
        plt.xlabel('时间 (分钟)', fontsize=12)
        plt.ylabel('睡眠阶段', fontsize=12)
        plt.yticks([])
        plt.xlim(0, time_points[-1])
        plt.grid(True, linestyle='--', alpha=0.6)
        plt.tight_layout()
        plt.savefig(save_path, dpi=150, bbox_inches='tight')
        plt.close()

    def _get_bmi_category(self, bmi):
        """获取BMI分类"""
        if bmi < 18.5:
            return "偏瘦"
        elif bmi < 24:
            return "正常"
        elif bmi < 28:
            return "超重"
        else:
            return "肥胖"


# ================== 用户界面组件 ==================
class UserProfileDialog(QDialog):
    """用户信息收集对话框"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("用户信息")
        self.setFixedSize(400, 300)

        layout = QVBoxLayout()

        # 表单布局
        form = QFormLayout()

        self.txtName = QLineEdit()
        self.txtName.setPlaceholderText("请输入姓名")
        form.addRow("姓名:", self.txtName)

        self.spinAge = QSpinBox()
        self.spinAge.setRange(18, 100)
        self.spinAge.setValue(30)
        form.addRow("年龄:", self.spinAge)

        self.cmbGender = QComboBox()
        self.cmbGender.addItems(["男", "女", "其他"])
        form.addRow("性别:", self.cmbGender)

        self.spinHeight = QSpinBox()
        self.spinHeight.setRange(100, 250)
        self.spinHeight.setValue(175)
        self.spinHeight.setSuffix(" cm")
        form.addRow("身高:", self.spinHeight)

        self.spinWeight = QSpinBox()
        self.spinWeight.setRange(30, 200)
        self.spinWeight.setValue(70)
        self.spinWeight.setSuffix(" kg")
        form.addRow("体重:", self.spinWeight)

        layout.addLayout(form)

        # 按钮
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)

        self.setLayout(layout)

    def get_profile(self):
        """获取用户配置"""
        return UserProfile(
            name=self.txtName.text(),
            age=self.spinAge.value(),
            gender=self.cmbGender.currentText(),
            height=self.spinHeight.value(),
            weight=self.spinWeight.value()
        )


class SleepReportWindow:
    """使用Psychopy展示睡眠报告"""

    def __init__(self, report_content):
        self.report_content = report_content
        self.win = None

    def show(self):
        """展示报告窗口"""
        try:
            # 创建全屏窗口
            mon = monitors.Monitor('reportMonitor')
            self.win = visual.Window(
                size=(1200, 800),
                color='#2c3e50',
                fullscr=False,
                monitor=mon,
                units='pix'
            )

            # 创建报告文本
            title = visual.TextStim(
                self.win,
                text='睡眠质量分析报告',
                height=40,
                color='white',
                pos=(0, 300)
            )

            content = visual.TextStim(
                self.win,
                text=self.report_content,
                height=24,
                color='#ecf0f1',
                pos=(0, 0),
                wrapWidth=1000
            )

            # 绘制报告
            title.draw()
            content.draw()
            self.win.flip()

            # 等待用户关闭
            core.wait(10)
            self.win.close()

        except Exception as e:
            logger.error(f"Psychopy报告展示失败: {str(e)}")
            QMessageBox.critical(None, "报告错误", f"无法展示报告:\n{str(e)}")


# ================== 主界面 ==================
    class MainWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            self.setWindowTitle("智能睡眠质量分析系统")
            self.setGeometry(100, 100, 1280, 800)
            self.collector = EEGDataCollector()
            self.analyzer = None
            self.api_key = None
            self.current_file = None
            self.user_profile = UserProfile()
            self.current_report = None

        # 设置应用样式
            self._set_application_style()
            self._setup_ui()
            self._connect_signals()

        # 添加状态栏
            self.statusBar = QStatusBar()
            self.setStatusBar(self.statusBar)
            self.statusBar.showMessage("就绪")

        def _set_application_style(self):
            """设置应用样式"""
            self.setStyleSheet("""
                QMainWindow {
                    background-color: #f0f2f5;
                }
            QWidget {
                font-family: 'Segoe UI', 'Microsoft YaHei';
                font-size: 10pt;
            }
            QPushButton {
                background-color: #4a86e8;
                color: white;
                border-radius: 4px;
                padding: 6px 12px;
                min-width: 80px;
            }
            QPushButton:hover {
                background-color: #3a76d8;
            }
            QPushButton:pressed {
                background-color: #2a66c8;
            }
            QPushButton:disabled {
                background-color: #a0a0a0;
            }
            QGroupBox {
                border: 1px solid #d0d0d0;
                border-radius: 6px;
                margin-top: 1ex;
                font-weight: bold;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px;
            }
            QTabWidget::pane {
                border: 1px solid #d0d0d0;
                border-radius: 4px;
                background: white;
            }
            QTabBar::tab {
                background: #e0e0e0;
                border: 1px solid #d0d0d0;
                border-bottom: none;
                padding: 8px 16px;
                border-top-left-radius: 4px;
                border-top-right-radius: 4px;
            }
            QTabBar::tab:selected {
                background: white;
                margin-bottom: -1px;
            }
            QTextEdit {
                border: 1px solid #d0d0d0;
                border-radius: 4px;
                background: white;
            }
            QProgressBar {
                border: 1px solid #d0d0d0;
                border-radius: 4px;
                text-align: center;
                background: white;
            }
            QProgressBar::chunk {
                background-color: #4a86e8;
                width: 10px;
            }
        """)

    def _setup_ui(self):
        """初始化界面"""
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        main_layout.setContentsMargins(15, 15, 15, 15)
        main_layout.setSpacing(15)

        # 标题栏
        title_layout = QHBoxLayout()
        title_label = QLabel("智能睡眠质量分析系统")
        title_label.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        title_label.setStyleSheet("color: #2c3e50;")
        title_layout.addWidget(title_label)
        title_layout.addStretch()

        # 用户信息标签
        self.user_label = QLabel("未设置用户")
        self.user_label.setFont(QFont("Microsoft YaHei", 10))
        self.user_label.setStyleSheet("color: #7f8c8d;")
        title_layout.addWidget(self.user_label)

        main_layout.addLayout(title_layout)

        # 主选项卡
        self.tab_widget = QTabWidget()
        main_layout.addWidget(self.tab_widget)

        # 数据采集选项卡
        self._setup_data_tab()

        # 数据分析选项卡
        self._setup_analysis_tab()

        # 系统信息选项卡
        self._setup_info_tab()

    def _setup_data_tab(self):
        """设置数据采集选项卡"""
        data_tab = QWidget()
        layout = QVBoxLayout(data_tab)

        # 设备连接组
        device_group = QGroupBox("设备连接")
        device_layout = QVBoxLayout()

        self.btn_connect = QPushButton("连接设备")
        self.btn_connect.setFixedHeight(40)
        self.btn_disconnect = QPushButton("断开连接")
        self.btn_disconnect.setFixedHeight(40)
        self.btn_disconnect.setEnabled(False)

        device_layout.addWidget(self.btn_connect)
        device_layout.addWidget(self.btn_disconnect)
        device_group.setLayout(device_layout)

        # 数据记录组
        record_group = QGroupBox("数据记录")
        record_layout = QVBoxLayout()

        self.btn_start = QPushButton("开始记录")
        self.btn_start.setFixedHeight(40)
        self.btn_start.setEnabled(False)
        self.btn_stop = QPushButton("停止记录")
        self.btn_stop.setFixedHeight(40)
        self.btn_stop.setEnabled(False)

        self.save_path_edit = QLineEdit()
        self.save_path_edit.setPlaceholderText("记录文件保存路径...")
        self.btn_browse = QPushButton("浏览...")

        path_layout = QHBoxLayout()
        path_layout.addWidget(self.save_path_edit)
        path_layout.addWidget(self.btn_browse)

        record_layout.addLayout(path_layout)
        record_layout.addWidget(self.btn_start)
        record_layout.addWidget(self.btn_stop)
        record_group.setLayout(record_layout)

        # 实时数据显示
        self.data_display = QTextEdit()
        self.data_display.setReadOnly(True)
        self.data_display.setFont(QFont("Consolas", 10))

        # 添加到布局
        layout.addWidget(device_group)
        layout.addWidget(record_group)
        layout.addWidget(QLabel("实时数据:"))
        layout.addWidget(self.data_display)

        self.tab_widget.addTab(data_tab, "数据采集")

    def _setup_analysis_tab(self):
        """设置数据分析选项卡"""
        analysis_tab = QWidget()
        layout = QVBoxLayout(analysis_tab)

        # 文件选择组
        file_group = QGroupBox("分析文件")
        file_layout = QVBoxLayout()

        self.analyze_path_edit = QLineEdit()
        self.analyze_path_edit.setPlaceholderText("选择要分析的CSV文件...")
        self.btn_analyze_browse = QPushButton("浏览...")
        self.btn_analyze = QPushButton("开始分析")
        self.btn_analyze.setEnabled(False)

        file_path_layout = QHBoxLayout()
        file_path_layout.addWidget(self.analyze_path_edit)
        file_path_layout.addWidget(self.btn_analyze_browse)

        file_layout.addLayout(file_path_layout)
        file_layout.addWidget(self.btn_analyze)
        file_group.setLayout(file_layout)

        # 分析进度
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setTextVisible(True)

        # 分析结果显示
        self.report_display = QTextEdit()
        self.report_display.setReadOnly(True)
        self.report_display.setFont(QFont("Microsoft YaHei", 10))

        # 报告操作按钮
        self.btn_show_report = QPushButton("全屏显示报告")
        self.btn_save_report = QPushButton("保存报告")
        self.btn_show_report.setEnabled(False)
        self.btn_save_report.setEnabled(False)

        btn_layout = QHBoxLayout()
        btn_layout.addWidget(self.btn_show_report)
        btn_layout.addWidget(self.btn_save_report)

        # 添加到布局
        layout.addWidget(file_group)
        layout.addWidget(self.progress_bar)
        layout.addWidget(QLabel("分析结果:"))
        layout.addWidget(self.report_display)
        layout.addLayout(btn_layout)

        self.tab_widget.addTab(analysis_tab, "数据分析")

    def _setup_info_tab(self):
        """设置系统信息选项卡"""
        info_tab = QWidget()
        layout = QVBoxLayout(info_tab)

        # 用户信息组
        user_group = QGroupBox("用户信息")
        user_layout = QVBoxLayout()

        self.btn_set_user = QPushButton("设置用户信息")
        self.btn_set_user.setFixedHeight(40)

        self.user_info_display = QTextEdit()
        self.user_info_display.setReadOnly(True)

        user_layout.addWidget(self.btn_set_user)
        user_layout.addWidget(self.user_info_display)
        user_group.setLayout(user_layout)

        # 系统信息组
        sys_group = QGroupBox("系统信息")
        sys_layout = QVBoxLayout()

        self.sys_info_display = QTextEdit()
        self.sys_info_display.setReadOnly(True)
        self.sys_info_display.setPlainText(SYSTEM_INFO + "\n" + COPYRIGHT_INFO)

        sys_layout.addWidget(self.sys_info_display)
        sys_group.setLayout(sys_layout)

        # API密钥设置
        api_group = QGroupBox("API设置")
        api_layout = QVBoxLayout()

        self.api_key_edit = QLineEdit()
        self.api_key_edit.setPlaceholderText("输入火山引擎ARK API密钥...")
        self.btn_save_api = QPushButton("保存API密钥")

        api_layout.addWidget(self.api_key_edit)
        api_layout.addWidget(self.btn_save_api)
        api_group.setLayout(api_layout)

        # 添加到布局
        layout.addWidget(user_group)
        layout.addWidget(api_group)
        layout.addWidget(sys_group)
        layout.addStretch()

        self.tab_widget.addTab(info_tab, "系统信息")

    def _connect_signals(self):
        """连接信号与槽"""
        # 设备连接
        self.btn_connect.clicked.connect(self._connect_device)
        self.btn_disconnect.clicked.connect(self._disconnect_device)

        # 数据记录
        self.btn_browse.clicked.connect(self._browse_save_path)
        self.btn_start.clicked.connect(self._start_recording)
        self.btn_stop.clicked.connect(self._stop_recording)

        # 数据分析
        self.btn_analyze_browse.clicked.connect(self._browse_analyze_file)
        self.btn_analyze.clicked.connect(self._start_analysis)
        self.btn_show_report.clicked.connect(self._show_fullscreen_report)
        self.btn_save_report.clicked.connect(self._save_report)

        # 用户信息
        self.btn_set_user.clicked.connect(self._set_user_profile)

        # API密钥
        self.btn_save_api.clicked.connect(self._save_api_key)

        # 数据收集器信号
        self.collector.data_received.connect(self._update_data_display)
        self.collector.status_updated.connect(self.statusBar.showMessage)
        self.collector.connection_status.connect(self._update_connection_status)
        self.collector.file_created.connect(self._on_file_created)

    async def _connect_device(self):
        """连接设备"""
        self.btn_connect.setEnabled(False)
        self.statusBar.showMessage("正在连接设备...")

        connected = await self.collector.connect_device()

        if connected:
            self.btn_connect.setEnabled(False)
            self.btn_disconnect.setEnabled(True)
            self.btn_start.setEnabled(True)
            self.user_label.setText(f"用户: {self.user_profile.name} | 设备: {self.collector.device_name}")
        else:
            self.btn_connect.setEnabled(True)
            self.btn_disconnect.setEnabled(False)
            self.btn_start.setEnabled(False)

    def _disconnect_device(self):
        """断开设备连接"""

        async def _async_disconnect():
            if self.collector.client and self.collector.connected:
                await self.collector.client.disconnect()
                self.collector.connected = False
                self.collector.connection_status.emit(False)
                self.btn_connect.setEnabled(True)
                self.btn_disconnect.setEnabled(False)
                self.btn_start.setEnabled(False)
                self.btn_stop.setEnabled(False)
                self.statusBar.showMessage("设备已断开")

        asyncio.create_task(_async_disconnect())

    def _update_connection_status(self, connected):
        """更新连接状态"""
        self.btn_connect.setEnabled(not connected)
        self.btn_disconnect.setEnabled(connected)
        self.btn_start.setEnabled(connected and not self.collector.is_recording)
        self.btn_stop.setEnabled(connected and self.collector.is_recording)

    def _browse_save_path(self):
        """浏览保存路径"""
        default_path = os.path.join(os.getcwd(), "data", f"sleep_data_{datetime.now().strftime('%Y%m%d')}")
        path, _ = QFileDialog.getSaveFileName(
            self,
            "保存记录文件",
            default_path,
            "CSV Files (*.csv)"
        )

        if path:
            self.save_path_edit.setText(path)

    def _start_recording(self):
        """开始记录"""
        path = self.save_path_edit.text()
        if not path:
            QMessageBox.warning(self, "路径错误", "请先设置记录文件保存路径")
            return

        if self.collector.start_recording(path):
            self.btn_start.setEnabled(False)
            self.btn_stop.setEnabled(True)
            self.statusBar.showMessage(f"开始记录到: {path}")

    def _stop_recording(self):
        """停止记录"""
        if self.collector.stop_recording():
            self.btn_start.setEnabled(True)
            self.btn_stop.setEnabled(False)
            self.statusBar.showMessage("记录已停止")

    def _update_data_display(self, data):
        """更新数据显示"""
        text = f"时间: {data['时间']}\n"
        text += f"Delta: {data['Delta']}\n"
        text += f"Theta: {data['Theta']}\n"
        text += f"Alpha: {data['Alpha']}\n"
        text += f"Beta: {data['Beta']}\n"
        text += f"Gamma: {data['Gamma']}\n"
        text += f"信号质量: {data['信号质量']}\n"

        self.data_display.append(text)

        # 保持滚动到底部
        scrollbar = self.data_display.verticalScrollBar()
        scrollbar.setValue(scrollbar.maximum())

    def _on_file_created(self, file_path):
        """文件创建后更新分析路径"""
        self.current_file = file_path
        self.analyze_path_edit.setText(file_path)
        self.btn_analyze.setEnabled(True)

    def _browse_analyze_file(self):
        """浏览分析文件"""
        path, _ = QFileDialog.getOpenFileName(
            self,
            "选择分析文件",
            os.getcwd(),
            "CSV Files (*.csv);;All Files (*)"
        )

        if path:
            self.analyze_path_edit.setText(path)
            self.btn_analyze.setEnabled(True)

    def _start_analysis(self):
        """开始分析"""
        path = self.analyze_path_edit.text()
        if not path or not os.path.exists(path):
            QMessageBox.warning(self, "文件错误", "请选择有效的CSV文件")
            return

        if not self.user_profile.name:
            QMessageBox.warning(self, "用户错误", "请先设置用户信息")
            return

        # 创建分析器
        self.analyzer = SleepAnalyzer(path, self.user_profile, self.api_key)
        self.analyzer.progress_updated.connect(self.progress_bar.setValue)
        self.analyzer.status_message.connect(self.statusBar.showMessage)
        self.analyzer.analysis_complete.connect(self._on_analysis_complete)

        # 禁用按钮
        self.btn_analyze.setEnabled(False)
        self.report_display.clear()
        self.report_display.setPlainText("分析中...请稍候")

        # 开始分析
        self.analyzer.start()

    def _on_analysis_complete(self, result):
        """分析完成处理"""
        self.btn_analyze.setEnabled(True)

        if "error" in result:
            self.report_display.setPlainText(f"分析失败: {result['error']}")
            return

        # 显示报告
        self.current_report = result
        self.report_display.setMarkdown(result["report_content"])

        # 启用报告按钮
        self.btn_show_report.setEnabled(True)
        self.btn_save_report.setEnabled(True)

        # 添加到用户历史
        self.user_profile.add_sleep_record({
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "score": result["score"]["score"],
            "file_path": result["report_path"]
        })

        # 更新用户信息显示
        self._update_user_info()

    def _show_fullscreen_report(self):
        """全屏显示报告"""
        if not self.current_report:
            return

        report_window = SleepReportWindow(self.current_report["report_content"])
        report_window.show()

    def _save_report(self):
        """保存报告"""
        if not self.current_report:
            return

        path, _ = QFileDialog.getSaveFileName(
            self,
            "保存报告",
            os.path.join(os.getcwd(), "reports", f"sleep_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"),
            "Markdown Files (*.md);;All Files (*)"
        )

        if path:
            try:
                with open(path, "w", encoding="utf-8") as f:
                    f.write(self.current_report["report_content"])
                self.statusBar.showMessage(f"报告已保存到: {path}")
            except Exception as e:
                QMessageBox.critical(self, "保存错误", f"无法保存报告: {str(e)}")

    def _set_user_profile(self):
        """设置用户信息"""
        dialog = UserProfileDialog(self)
        if dialog.exec() == QDialog.Accepted:
            self.user_profile = dialog.get_profile()
            self._update_user_info()
            self.statusBar.showMessage(f"用户信息已更新: {self.user_profile.name}")

    def _update_user_info(self):
        """更新用户信息显示"""
        info = f"姓名: {self.user_profile.name}\n"
        info += f"年龄: {self.user_profile.age}岁\n"
        info += f"性别: {self.user_profile.gender}\n"
        info += f"身高: {self.user_profile.height}cm\n"
        info += f"体重: {self.user_profile.weight}kg\n"
        info += f"BMI: {self.user_profile.bmi:.1f}\n\n"

        trends = self.user_profile.get_sleep_trends()
        if trends:
            info += f"平均睡眠评分: {trends['avg_score']:.1f}\n"
            if trends['weekly_avg']:
                info += f"最近7天平均: {trends['weekly_avg']:.1f}\n"
            info += f"趋势: {trends['trend']}\n"

        self.user_info_display.setPlainText(info)
        self.user_label.setText(
            f"用户: {self.user_profile.name} | 设备: {self.collector.device_name if self.collector.connected else '未连接'}")

    def _save_api_key(self):
        """保存API密钥"""
        self.api_key = self.api_key_edit.text()
        self.statusBar.showMessage("API密钥已保存")

    def closeEvent(self, event):
        """关闭事件处理"""
        if self.collector.is_recording:
            reply = QMessageBox.question(
                self,
                "正在记录",
                "当前正在记录数据，确定要退出吗？",
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No
            )

            if reply == QMessageBox.No:
                event.ignore()
                return

        # 停止所有后台任务
        if self.collector.connected:
            asyncio.create_task(self._disconnect_device())

        if self.analyzer and self.analyzer.isRunning():
            self.analyzer.terminate()

        event.accept()


# ================== 应用入口 ==================
def main():
    # 创建应用
    app = QApplication(sys.argv)

    # 设置异步事件循环
    loop = qasync.QEventLoop(app)
    asyncio.set_event_loop(loop)

    # 创建主窗口
    window = MainWindow()
    window.show()

    # 运行应用
    with loop:
        loop.run_forever()


if __name__ == "__main__":
    main()